package project

import (
	bytes2 "bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/google/uuid"
	"io/fs"
	"lib/utils/fileutil"
	"os"
	"path/filepath"
	"strings"
)

type NodeType int

const (
	NodeFile NodeType = 0
	NodeDir  NodeType = 1
)

type Node struct {
	NodeId       string   `json:"nodeId"`
	NodeName     string   `json:"nodeName"`
	NodeType     NodeType `json:"nodeType"`
	NodePath     string   `json:"nodePath"`
	NodeContent  string   `json:"nodeContent"`
	ParentNodeId string   `json:"parentNodeId"`
	ChildNodes   []*Node  `json:"childNodes"`
	IsHidden     bool     `json:"isHidden"`
}

func InitEmptyProject(root string, projectName string) error {
	var nodes []Node

	bytes, readJsonErr := os.ReadFile("D:\\codes\\liyuncong\\go-demo\\go-gorm-sqlite\\project\\project_struct.json")
	if readJsonErr != nil {
		return errors.New(fmt.Sprintf("ReadJsonFileErr %v \n", readJsonErr))
	}
	convertJsonErr := json.Unmarshal(bytes, &nodes)
	if convertJsonErr != nil {
		return errors.New(fmt.Sprintf("ConvertJsonErr %v \n", convertJsonErr))
	}
	// custom make dir name
	newProjectName := getUUID()
	if makeNameErr := fileutil.CustomMaKeDirectory(newProjectName, projectName, root); makeNameErr != nil {
		return errors.New(fmt.Sprintf("DirMakeCustomNameErr %v \n", makeNameErr))
	}
	for _, node := range nodes {
		// type 0: dir , 1: file
		target := filepath.Join(root, newProjectName, node.NodePath)
		if node.NodeType == 0 {
			// 1. mkdir
			if mkdirErr := os.MkdirAll(target, os.ModePerm); mkdirErr != nil {
				return errors.New(fmt.Sprintf("MkdirErr %v \n", mkdirErr))
			}
			// 2. create hidden ini file
			sourceName := filepath.Base(target)
			dirParentPath := filepath.Dir(target)
			if makeNameErr := fileutil.CustomMaKeDirectory(sourceName, sourceName, dirParentPath); makeNameErr != nil {
				return errors.New(fmt.Sprintf("DirMakeCustomNameErr %v \n", makeNameErr))
			}
		} else {
			parent := filepath.Dir(target)
			if _, statErr := os.Stat(parent); statErr != nil {
				if mkdirErr := os.MkdirAll(parent, os.ModePerm); mkdirErr != nil {
					return errors.New(fmt.Sprintf("ParentMkdirErr %v \n", mkdirErr))
				}
			}
			if _, createFileErr := os.Create(target); createFileErr != nil {
				return errors.New(fmt.Sprintf("CreateFileErr %v \n", createFileErr))
			}

			if writeFileErr := os.WriteFile(target, bytes2.NewBufferString(node.NodeContent).Bytes(), os.ModePerm); writeFileErr != nil {
				return errors.New(fmt.Sprintf("WriteFileErr %v \n", writeFileErr))
			}
		}
		if node.IsHidden {
			if setHiddenErr := fileutil.SetHidden(target); setHiddenErr != nil {
				return errors.New(fmt.Sprintf("SetHiddenErr %v \n", setHiddenErr))
			}
		}
	}
	return nil
}

func Load(source string) (Node, error) {
	// k-v path-uuid
	var nodeMap = map[string]*Node{}

	const iniFilename = "desktop.ini"

	var rootNode = &Node{
		NodeId:       "0",
		NodeName:     "",
		NodeType:     NodeDir,
		NodePath:     "",
		ParentNodeId: "0",
		ChildNodes:   []*Node{},
		IsHidden:     false,
	}

	err := filepath.Walk(source, func(path string, info fs.FileInfo, err error) error {
		nodeName := ""
		var nodeType NodeType
		if info.IsDir() {
			projectName, readErr := fileutil.ReadCustomDirectoryName(filepath.Join(path, iniFilename))
			if readErr != nil {
				nodeName = info.Name()
			} else {
				nodeName = projectName
			}
			nodeType = NodeDir
		} else {
			nodeName = info.Name()
			nodeType = NodeFile
		}
		currentNode := &Node{
			NodeId:       getUUID(),
			NodeName:     nodeName,
			NodeType:     nodeType,
			NodePath:     path,
			ParentNodeId: getParentId(nodeMap, path),
			ChildNodes:   []*Node{},
			IsHidden:     strings.EqualFold(nodeName, iniFilename),
		}
		if _, existed := nodeMap[path]; !existed {
			nodeMap[path] = currentNode
		}
		AddNode(rootNode, currentNode)
		return err
	})
	if err != nil {
		return Node{}, err
	}
	return *rootNode, nil
}

func AddNode(root *Node, node *Node) {
	if root.NodeId == "0" {
		root.NodeId = node.NodeId
		root.NodeName = node.NodeName
		root.NodeType = node.NodeType
		root.NodePath = node.NodePath
		root.ParentNodeId = node.ParentNodeId
		root.ChildNodes = node.ChildNodes
		root.IsHidden = node.IsHidden
		return
	}
	if node.ParentNodeId == root.NodeId {
		root.ChildNodes = append(root.ChildNodes, node)
		return
	}
	if len(root.ChildNodes) == 0 {
		return
	} else {
		for _, child := range root.ChildNodes {
			AddNode(child, node)
		}
	}
}

func QueryNode(root *Node, nodeId string) *Node {
	if root.NodeId == nodeId {
		return root
	}
	if len(root.ChildNodes) != 0 {
		for _, child := range root.ChildNodes {
			if r := QueryNode(child, nodeId); r != nil {
				return r
			}
		}
	}
	return nil
}

func UpdateNode(root *Node, nodeId string, newNode *Node) bool {
	if root.NodeId == nodeId {
		root.NodeId = newNode.NodeId
		root.NodeName = newNode.NodeName
		root.NodeType = newNode.NodeType
		root.NodePath = newNode.NodePath
		root.ParentNodeId = newNode.ParentNodeId
		root.ChildNodes = newNode.ChildNodes
		root.IsHidden = newNode.IsHidden
		return true
	}
	if len(root.ChildNodes) != 0 {
		for _, child := range root.ChildNodes {
			if UpdateNode(child, nodeId, newNode) {
				return true
			}
		}
	}
	return false
}

func RemoveNode(root *Node, nodeId string) bool {
	if root.NodeId == nodeId {
		root.NodeId = ""
		root.NodeName = ""
		root.NodeType = NodeFile
		root.NodePath = ""
		root.ParentNodeId = ""
		root.ChildNodes = nil
		root.IsHidden = true
		return true
	}
	if len(root.ChildNodes) != 0 {
		for _, child := range root.ChildNodes {
			if RemoveNode(child, nodeId) {
				return true
			}
		}
	}
	return false
}

func getUUID() string {
	newUUID, _ := uuid.NewUUID()
	return newUUID.String()
}

func getParentId(nodeMap map[string]*Node, source string) string {
	parent := filepath.Dir(source)
	if node, existed := nodeMap[parent]; existed {
		return node.NodeId
	}
	return ""
}
